#include <i386.h>
#include <kbd.h>
#include <scanToASCII.h>


typedef struct _kbdrequest{
    
    DEVICE* dev;
    void* buf;
    int buflen;
    int bufsz;
    struct _kbdrequest* next;
            
} KBD_REQUEST;


///
static char kbd_eof = 4;
static char kbd_buf[4];
static int  kbd_bufsz = 0;

KBD_REQUEST* req_queue = NULL;
///


void kbd_pushchar(char c);
void kbd_enqueue(KBD_REQUEST* req);
KBD_REQUEST* kbd_dequeue();
KBD_REQUEST* kbd_peek();
void kbd_complete();


/** Keyboard Top Half **/

void kbd_enqueue(KBD_REQUEST* req){

    req->next = NULL;
    if(req_queue == NULL){
        req_queue = req;
    }else{
        KBD_REQUEST* cur = req_queue;
        while(cur) cur = cur->next;
        cur->next = req;
    }
}

KBD_REQUEST* kbd_dequeue(){

    if(req_queue != NULL){
        KBD_REQUEST* tmp = req_queue;
        req_queue = req_queue->next;
        return tmp;
    }else{
        return NULL;
    }   
}

KBD_REQUEST* kbd_peek(){

    return req_queue;
}

// request finished, wake up process
void kbd_complete(){

    KBD_REQUEST* req = kbd_dequeue();
    
    // return number of bytes read
    req->dev->dvowner->rc = req->bufsz;
    
    unblock(req->dev->dvowner);
    ready(req->dev->dvowner);
    
    kfree(req);
    
}

// push char from hardware up to buffers
void kbd_pushchar(char c){
    KBD_REQUEST* top;
    
    top = kbd_peek();
    
    if(top){
    
        if(c == 10){
            ((unsigned char*)top->buf)[top->bufsz++] = '\n';
            kbd_complete();
            return;
        }

        if(c == kbd_eof){
            top->dev->dveof = TRUE;
            kbd_complete();
            return;
        }
    
        // there's a request
        ((unsigned char*)top->buf)[top->bufsz++] = c;
        
        // echo the char if we're serving that device    
        if(top->dev->dvnum == DEVICE_ECHOKBD){
            kprintf("%c", c);
        }
        
        // check for request completion conditions
        if(top->bufsz == top->buflen){
            
            kbd_complete();
            return;    
        }
               
    }else{
        // buffer it if possible
        if(kbd_bufsz < 4){
            //kprintf("kbd_buf += %c\n", c); 
            kbd_buf[kbd_bufsz++] = c;
        }
    }
    
}

extern int kbd_open(DEVICE* dev){
    
    //kprintf("kbd_open: enabling keyboard...\n");
    
    enable_irq(1, 0); // enable
    
    
    return 0;
}

extern int kbd_close(DEVICE* dev){
    
    enable_irq(1, 1); // disable
    
    return 0;
}

extern int kbd_write(DEVICE* dev, void* buf, int buflen){
    return -1;
}

extern int kbd_read(DEVICE* dev, void* buf, int buflen){
    KBD_REQUEST* req; 

    if(dev->dveof == TRUE){
        return -2;
    }

    req = (KBD_REQUEST*)kmalloc(sizeof(KBD_REQUEST));
    
    if(!req)
    return -1;
    
    req->dev = dev;
    req->buf = buf;
    req->buflen = buflen;
    req->bufsz = 0;
    
    if(kbd_bufsz > 0){
        blkcopy(req->buf, kbd_buf, kbd_bufsz);
        req->bufsz = kbd_bufsz;
        kbd_bufsz = 0;
    }
    
    if(req->bufsz == req->buflen){
        kbd_enqueue(req);        
        kbd_complete();
        return;
    }
    
    kbd_enqueue(req);
    
    return 0;
}

extern int kbd_ioctl(DEVICE* dev, unsigned long cmd){
    
    switch(cmd){
        case 49:
            kbd_eof = va_arg(__sysioctl_args, int); // XXX int might be wrong
            // write to control port...
            outb(0x64, kbd_eof);
            
            return 0;
            
        break;
    }
    
    // unknown command
    return -1; 
    
    
}

/** Keyboard Bottom Half **/
extern int kbd_iint(){
    
    unsigned char byte;
    unsigned char ascii;
    
    //kprintf("Keyboard interrupt!\n");
    
    byte = inb(0x64);
    
    if(byte & 0x01){
        
        byte = inb(0x60);
        
        ascii = kbtoa(byte);

        //kprintf("kbd_iint: %d / %d \n", byte, ascii);        

        if(ascii != NOCHAR && ascii != 0)
            kbd_pushchar( ascii );
                         
    }
    
}

